import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.io.BufferedWriter;
import java.util.HashSet;

/**
 * Represents token (single word).
 */
public class Token extends TextElement
{
    /**
     * The word itself.
     */
    public String surface;

    /**
     * Set of morphological analyses.
     */
    public Analysis[] analyses;

    /**
     * Set of all possible tags for the token, based on set of possible analyses.
     */
    public HashSet<Tag> tagSet = new HashSet<Tag>();

    /**
     * Correct tag for the token. Determined during comparison with reference corpus.
     */
    public Tag rightTag = null;

    /**
     * Currently chosen tag for the token. May be changed during training/evaluation.
     */
    public Tag currentTag = null;

    /**
     * Construct from DOM element.
     */
    public Token(Element tokenElement, Sentence parentSentence) throws Throwable
    {
        id = tokenElement.getAttribute("id");
        parent = parentSentence;

        // save DOM element if needed
        if (flagModifyDOM)
            element = tokenElement;

        surface = tokenElement.getAttribute("surface");
        NodeList analysesNodes = tokenElement.getChildNodes();

        // build set of analyses
        analyses = new Analysis[analysesNodes.getLength()];
        for (int i = 0; i < analysesNodes.getLength(); i++)
        {
            Element analysisElem = (Element) analysesNodes.item(i);
            analyses[i] = new Analysis(analysisElem, this);
            tagSet.add(analyses[i].tag);
            if (analyses[i].score > 0.0)
                currentTag = analyses[i].tag;
        }

        // determine correct analysis if stored
        String rightAnalysisId = tokenElement.getAttribute("rightAnalysis");
        if (!rightAnalysisId.isEmpty())
        {
            int rightAnalysisIndex = Integer.parseInt(rightAnalysisId) - 1;
            if (rightAnalysisIndex >= 0)
            {
                Analysis rightAnalysis = analyses[rightAnalysisIndex];
                rightTag = analyses[rightAnalysisIndex].tag;
                if (rightAnalysis.score > 0)
                    currentTag = rightTag;
            }
        }
    }

    /**
     * Error status of the token.
     */
    public enum Status
    {
        CORRECT, // correct tag chosen
        ERROR, // incorrect tag chosen
        UNDEFINED // correct tag can't be determined
    }

    /**
     * Check current status.
     */
    public Status GetStatus()
    {
        assert tagSet.contains(currentTag);
        assert rightTag == null || tagSet.contains(rightTag);

        if (rightTag == null)
            return Status.UNDEFINED;
        else if (!currentTag.equals(rightTag))
            return Status.ERROR;
        else
            return Status.CORRECT;
    }

    /**
     * Compare corresponding token of evaluation and reference corpora.
     * Used by FindErrors routine.
     */
    public void CompareToReference(Token referenceToken, BufferedWriter errorWriter)
            throws Throwable
    {
        assert this.surface.equals(referenceToken.surface);
        assert this.id.equals(referenceToken.id);

        // tag of marked analysis in reference token
        Tag referenceTag = referenceToken.currentTag;

        // will point at correct tag for evaluation token, if it has one
        Tag evaluationRightTag = null;

        // will point at analysis of evaluation token which has correct tag
        String rightAnalysisId = null;

        if (referenceTag != null)
        {
            // search for correct analysis in the list of evaluation token's analyses
            for (int i = 0; i < this.analyses.length; i++)
            {
                Analysis evaluationAnalysis = this.analyses[i];
                if (evaluationAnalysis.tag.CompareToReference(referenceTag).isEmpty())
                {
                    rightAnalysisId = evaluationAnalysis.id;
                    evaluationRightTag = evaluationAnalysis.tag;
                    if (evaluationAnalysis.score > 0)
                    {
                        // if there is evaluation analysis with positive score which has right tag
                        // we want it to be chosen as 'current' tag for this token
                        this.currentTag = evaluationRightTag;
                        break;
                    }
                }
            }

            Tag evaluationCurrentTag = this.currentTag;

            // check if currently chosen and correct tag for evaluation token differ
            if (evaluationRightTag != null && !evaluationCurrentTag.equals(evaluationRightTag))
            {
                // Log the error including description of differences between
                String diff = evaluationCurrentTag.CompareToReference(this.rightTag);
                errorWriter.write(this.GetFullID() + " " + evaluationCurrentTag.base + " "
                        + evaluationRightTag.base + " " + diff);
                errorWriter.newLine();
            }
        }

        // save ID of right analysis in XML
        if (this.element != null && rightAnalysisId != null)
            this.element.setAttribute("rightAnalysis", rightAnalysisId);
    }
}
